import { CdkDragMove } from '@angular/cdk/drag-drop';
import { Component, HostListener } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { DomSanitizer } from '@angular/platform-browser';
import LRUCache from 'lru-cache';
import { BaseComponent } from 'src/api/base_component';
import { Program, ProgramType, Tag, TagType } from 'src/api/dto';
import { Util } from 'src/util';
import TreeMap from 'ts-treemap';
import { AddTagArg, AddTagDialog } from './add-tag/add-tag-dialog';

class ProgramLineElement {
  left: number = 0;
  width: number = 0;
  color: string = '';
  program!: Program;
  constructor(left: number, width: number, color: string, program: Program) {
    this.left = left;
    this.width = width;
    this.color = color;
    this.program = program;
  }
}

class TagLineElement {
  left: number = 0;
  width: number = 0;
  color: string = '';
  tag!: Tag;

  constructor(left: number, width: number, color: string, tag: Tag) {
    this.left = left;
    this.width = width;
    this.color = color;
    this.tag = tag;
  }
}
@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.less'],
})
export class MainComponent extends BaseComponent {
  constructor(public dialog: MatDialog, private sanitizer: DomSanitizer) {
    super();
  }

  board = window.innerWidth + 'px';

  date = new Date();

  programLines: Array<Program> = [];
  programLineMap: Map<number, Program> = new Map();
  programLineTreeMap: TreeMap<number, Program> = new TreeMap();
  programLineElements: Array<ProgramLineElement> = [];

  programType: Array<ProgramType> = [];
  programTypeMap: Map<string, ProgramType> = new Map();
  displayedColumns: string[] = ['id', 'program_name', 'title', 'begin_time', 'end_time'];

  tagLine: Tag[] = [];
  tagLineMap: Map<number, Tag> = new Map();
  tagLineTreeMap: TreeMap<number, Tag> = new TreeMap();
  tagLineElements: Array<TagLineElement> = [];

  tagType: TagType[] = [];
  tagTypeMap: Map<string, TagType> = new Map();

  lineStartTime = 0;
  lineEndTime = 0;
  dateStartTime = 0;
  dateEndTime = 0;

  timeAxisMove(e: CdkDragMove) {
    // console.log(e.source.element.nativeElement.offsetLeft)
  }



  async updateDate(date: Date|void) {
    if (date) {
      this.date = date;
      [this.dateStartTime, this.dateEndTime] = Util.getDateRange(this.date);
      [this.lineStartTime, this.lineEndTime] = Util.getDateRange(this.date);
    }

    await this.updateProgramLineByDate();
    this.programLineMap = new Map();
    this.programLineTreeMap = new TreeMap()
    this.programLines.forEach((p) => {
      const beginTime = new Date(p.begin_time).getTime();
      this.programLineMap.set(p.id, p);
      this.programLineTreeMap.set(beginTime, p);
    });
    this.programType = await this.api().get_program_types();
    this.programType.forEach((t) => {
      this.programTypeMap.set(t.name, t);
    });

    await this.updateTagLineByDate();
    this.tagLineMap = new Map();
    this.tagLineTreeMap = new TreeMap()
    this.tagLine.forEach((t) => {
      this.tagLineMap.set(t.id, t);
      this.tagLineTreeMap.set(new Date(t.begin_time).getTime(), t);
    });
    this.tagType = await this.api()?.get_tag_types();
    this.tagType.forEach((t) => {
      this.tagTypeMap.set(t.name, t);
    });

    this.draw();
  }

  async updateTagLineByDate() {
    this.tagLine = await this.api().get_tag_line(Util.dateToString(this.date));
  }

  draw() {
    this.drawLines();
    this.drawTimeLine();
  }

  async drawLines() {
    const sampleTimes: Array<number> = [];
    for (let i = 0; i < this.width; ++i) {
      sampleTimes.push(this.lineStartTime + (i / this.width) * (this.lineEndTime - this.lineStartTime));
    }
    // console.log(sampleTimes)
    this.programLineElements = [];
    sampleTimes.forEach((i: number, idx) => {
      const entry = this.programLineTreeMap.floorEntry(i);
      if (!entry) {
        // 表示这里是没有programLine的
        return;
      }
      const p = entry![1];
      const beginTime = new Date(p.begin_time).getTime();
      const endTime = new Date(p.end_time).getTime();
      if (endTime >= i) {
        if (
          this.programLineElements.length == 0 ||
          this.programLineElements[this.programLineElements.length - 1].program.id != p.id
        ) {
          const c = this.programTypeMap.get(p.name)!.color;
          this.programLineElements.push(new ProgramLineElement(idx, 1, c, p));
        } else {
          this.programLineElements[this.programLineElements.length - 1].width += 1;
        }
      }
    });
    // console.log(this.programLineElements)

    this.tagLineElements = [];
    sampleTimes.forEach((i: number, idx) => {
      const entry = this.tagLineTreeMap.floorEntry(i);
      if (!entry) {
        // 表示这里是没有tagLine的
        return;
      }
      const t = entry![1];
      const beginTime = new Date(t.begin_time).getTime();
      const endTime = new Date(t.end_time).getTime();
      if (endTime >= i) {
        if (this.tagLineElements.length == 0 || this.tagLineElements[this.tagLineElements.length - 1].tag.id != t.id) {
          const c = this.tagTypeMap.get(t.name)!.color;
          this.tagLineElements.push(new TagLineElement(idx, 1, c, t));
        } else {
          this.tagLineElements[this.tagLineElements.length - 1].width += 1;
        }
      }
    });
  }

  timeLineLeft = 0;

  drawTimeLine() {
    const now = new Date();
    if (this.date.getDate() == now.getDate()) {
      console.log('draw timeline');
      this.timeLineLeft = ((now.getTime() - this.lineStartTime) / (this.lineEndTime - this.lineStartTime)) * this.width;
    } else {
      this.timeLineLeft = -1;
    }
  }

  programLineElementOnClick(e: ProgramLineElement) {
    // console.log(e.program)
    this.board = Program.toString(e.program);
  }

  programLineElementOnMouseEnter(e: ProgramLineElement) {
    this.board = Program.toString(e.program);
    // console.log(e.program)
  }

  async programLineElementOnMouseMove(e: ProgramLineElement) {
    console.log('program line element move');
    const timestamp = Math.round(
      (this.mouseX / this.width) * (this.lineEndTime - this.lineStartTime) + this.lineStartTime
    );

    // new Date(timestamp)
    // console.log(e.x, e.y, leftTime, timestamp, rightTime);
    if (this.showCapture) {
      const path = await this.api().get_last_capture_path(timestamp);
      console.log(path);
      if (this.blobUrlCache.has(path)) {
        this.imgSrc = this.blobUrlCache.get(path)!;
      } else if (path.length == 0) {
        this.imgSrc = 'assets/black.jpg';
      } else {
        this.api()
          .read_file_byte_list(path)
          .then((f) => {
            const blobUrl = Util.intArrayToBlobUrl(f, 'image/png');
            this.imgSrc = blobUrl;
            // this.imgSrc = 'data:image/png;base64,' + f;
            this.blobUrlCache.set(path, blobUrl);
          });
      }
    }
  }

  programLineElementOnMouseLeave(e: ProgramLineElement) {}

  tagLineElementOnClick(e: TagLineElement) {
    console.log('click');
  }

  tagLineElementOnDblClick(e: TagLineElement) {
    // console.log('dbl click')
    this.notListenKey = true;

    const conf = new MatDialogConfig<AddTagArg>();
    conf.data = new AddTagArg();
    conf.data.id = e.tag.id;
    conf.data.name = e.tag.name;
    conf.data.beginTime = new Date(e.tag.begin_time).getTime();
    conf.data.endTime = new Date(e.tag.end_time).getTime();
    conf.data.note = e.tag.note;
    conf.data.tagTypeMap = this.tagTypeMap;
    const dialogRef = this.dialog.open(AddTagDialog, conf);

    dialogRef.afterClosed().subscribe((result: AddTagArg) => {
      console.log('The dialog was closed', result);
      this.notListenKey = false;
      if (result && result.name.length != 0) {
        this.api()
          .update_tag(result.id, result.name, result.beginTime, result.endTime, result.note)
          .then((success) => {
            if (success) {
              this.cancelSelectTagTimeRange();
              this.updateDate();
            } else {
              console.log('error');
            }
          });
      }
    });
  }

  tagLineElementOnMouseEnter(e: TagLineElement) {}

  tagLineElementOnMouseLeave(e: TagLineElement) {}

  mouseX = 0;
  mouseXStart = -1;
  mouseXEnd = -1;
  startLineDisplay = 'none';
  endLineDisplay = 'none';

  drag(e: any) {
    console.log(e);
  }

  notListenKey = false;

  @HostListener('document:keydown', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    console.log(event);
    if (this.notListenKey) {
      return;
    }
    if (event.key == 'c') {
      this.cancelSelectTagTimeRange();
    } else if (event.key == 'a') {
      if (this.inLines) {
        this.mouseXStart = this.mouseX;
        this.startLineDisplay = 'block';
        this.mouseXEnd = -1;
        this.endLineDisplay = 'none';
      }
    } else if (event.key == 'b') {
      if (this.inLines && this.mouseXStart != -1) {
        this.mouseXEnd = this.mouseX;
        this.endLineDisplay = 'block';
      }
    } else if (event.key == 't') {
      this.addTag();
    } else if (event.key == 'ArrowLeft') {
      this.lineStartTime -= new Date(1000 * 60 * 60).getTime();
      this.lineEndTime -= new Date(1000 * 60 * 60).getTime();
      this.updateDate();
    } else if (event.key == 'ArrowRight') {
      this.lineStartTime += new Date(1000 * 60 * 60).getTime();
      this.lineEndTime += new Date(1000 * 60 * 60).getTime();
      this.updateDate();
    } else if (event.key == '=') {
      this.lineStartTime += new Date(1000 * 60 * 60).getTime();
      this.lineEndTime -= new Date(1000 * 60 * 60).getTime();
      this.updateDate();
    } else if (event.key == '-') {
      this.lineStartTime -= new Date(1000 * 60 * 60).getTime();
      this.lineEndTime += new Date(1000 * 60 * 60).getTime();
      this.updateDate();
    }
  }

  cancelSelectTagTimeRange() {
    this.mouseXStart = -1;
    this.mouseXEnd = -1;
    this.startLineDisplay = 'none';
    this.endLineDisplay = 'none';
  }

  @HostListener('document:keyup', ['$event'])
  onKeyUp(event: KeyboardEvent) {
    console.log(event);
  }

  async linesOnMouseMove(e: MouseEvent) {
    this.mouseX = e.clientX;
    this.board = String(
      new Date((this.mouseX / this.width) * (this.lineEndTime - this.lineStartTime) + this.lineStartTime)
    );
  }

  inLines = false;

  linesOnMouseEnter(e: MouseEvent) {
    this.inLines = true;
    console.log('enter');
  }

  linesOnMouseLeave(e: MouseEvent) {
    this.inLines = false;
    console.log('leave');
  }

  linesOnDragStart(e: DragEvent) {
    console.log('drag');
    console.log(e);
  }
  linesOnDragEnd(e: DragEvent) {
    console.log(e);
  }

  async updateProgramLineByDate() {
    this.programLines = await this.api().get_program_line(Util.dateToString(this.date));
  }

  width = window.innerWidth;
  @HostListener('window:resize', ['$event'])
  onResize(event: any) {
    console.log('Window resized');
    this.width = window.innerWidth;
    this.board = this.width + 'px';
    this.draw();
  }

  ngOnInit() {
    setTimeout(() => {
      [this.dateStartTime, this.dateEndTime] = Util.getDateRange(this.date);
      [this.lineStartTime, this.lineEndTime] = Util.getDateRange(this.date);
      this.updateDate();
    }, 500);
    setInterval(() => {
      this.updateDate();
    }, 5000);
  }

  ngAfterViewInit() {}

  addTag() {
    this.notListenKey = true;
    const conf = new MatDialogConfig<AddTagArg>();
    conf.data = new AddTagArg();
    conf.data.id = -1;
    conf.data.beginTime =
      this.lineStartTime + (this.mouseXStart / this.width) * (this.lineEndTime - this.lineStartTime);
    conf.data.endTime = this.lineStartTime + (this.mouseXEnd / this.width) * (this.lineEndTime - this.lineStartTime);
    conf.data.name = '';
    conf.data.note = '';
    const dialogRef = this.dialog.open(AddTagDialog, conf);
    dialogRef.afterClosed().subscribe((result: AddTagArg) => {
      console.log('The dialog was closed', result);
      this.notListenKey = false;
      if (result && result.name.length != 0) {
        this.api()
          .add_tag(result.name, result.beginTime, result.endTime, result.note)
          .then((success) => {
            if (success) {
              this.cancelSelectTagTimeRange();
              this.updateDate();
            } else {
              console.log('error');
            }
          });
      }
    });
  }

  hide() {
    this.api().hide();
  }

  openDialog() {
    this.dialog.open(ClockDialog, {
      width: '250px',
    });
  }
  clock_value = '00:00:10';

  async add_clock() {
    const [hours, minutes, seconds] = this.clock_value.split(':').map(Number);
    let job_id = await this.api().set_clock(hours * 3600 + minutes * 60 + seconds);
    // const d = new Date();
    // d.setHours(hours);
    // d.setMinutes(minutes);
    // d.setSeconds(seconds);
    // // this.jobs.push(job_id);
    // this.tmp = job_id;
  }

  getSafeUrl(blobUrl: string) {
    if (blobUrl.startsWith("asserts")) {
      return blobUrl
    }
    return this.sanitizer.bypassSecurityTrustUrl(blobUrl);
  }

  imgSrc = 'assets/black.jpg';
  // imgSrc = 'assets/logo.png';
  showCapture = true;
  blobUrlCache = new LRUCache({
    max: 500,
    ttl: 1000 * 60 * 5, // 存活5分钟
    // // 最大字节数
    // maxSize: 5000,
    // // 每个value的字节数
    // sizeCalculation: (value, key) => {
    //   return 1;
    // },

    dispose: (key: string, value: string) => {
      URL.revokeObjectURL(value);
    },
  });
}

@Component({
  selector: 'clock-dialog',
  templateUrl: 'clock-dialog.html',
})
export class ClockDialog {}
